package ao.ai.ml.algo.supervised.classification.decison;

import ao.ai.ml.algo.supervised.classification.model.learner.ext.BinaryLearner;
import ao.ai.ml.algo.supervised.model.example.Example;
import ao.ai.ml.algo.supervised.model.hypothesis.ext.BinaryClassificationHypothesis;
import ao.ai.ml.model.feature_set.ext.cat.bin.SingleBinaryFeature;
import ao.ai.ml.model.feature_set.ext.num.NumericalFeatureList;
import ao.ai.ml.model.feature_set.impl.BinaryScalar;
import ao.supervised.classifier.raw.Classifier;
import ao.supervised.classifier.raw.ClassifierImpl;
import ao.supervised.decision.classification.raw.Prediction;
import ao.supervised.decision.input.raw.example.Context;
import ao.supervised.decision.input.raw.example.ContextImpl;
import ao.supervised.decision.input.raw.example.Datum;
import ao.supervised.decision.input.raw.example.LearningSet;
import ao.supervised.decision.tree.GeneralTreeLearner;
import ao.util.math.rand.Rand;
import com.google.common.collect.Iterables;

import java.util.LinkedList;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: Mable
 * Date: Feb 28, 2010
 * Time: 8:06:44 PM
 */
public class MinimumInformationBinaryClassifier
        implements BinaryLearner
{
    //-------------------------------------------------------------------------
    @Override
    public BinaryClassificationHypothesis learn(
            List<? extends Example<
                                ? extends NumericalFeatureList,
                                ? extends SingleBinaryFeature>>
                data)
    {
        assert data != null &&
               ! data.isEmpty();

        final Example<? extends NumericalFeatureList,
                      ? extends SingleBinaryFeature>
            arbitraryExample = Iterables.get(data, 0);

        final LearningSet examples = new LearningSet();
        final Classifier learner  =
                new ClassifierImpl(
                        new GeneralTreeLearner());

        for (Example<? extends NumericalFeatureList,
                     ? extends SingleBinaryFeature>
                example : data)
        {
            examples.add(
                    function(
                            example.output().binaryCategory(),
                            example.input().doubleValues()
                    )
            );
        }

        learner.set( examples );

        return new BinaryClassificationHypothesis() {
            @Override public double probabilityOfPositive(
                    NumericalFeatureList input) {
                Prediction guess = learner.classify(
                        context(input.doubleValues()));
                return guess.probabilityOf(
                        new Datum(true));
            }

            @Override public double probabilityOf(
                    NumericalFeatureList input, int categoryIndex) {
                return (categoryIndex == 0)
                       ? 1.0 - probabilityOfPositive(input)
                       :       probabilityOfPositive(input);
            }

            @Override public SingleBinaryFeature predict(
                    NumericalFeatureList input) {
                return new BinaryScalar(
                        Rand.nextBoolean(probabilityOfPositive(input)),
                        arbitraryExample.output().type());
            }
        };
    }


    //-------------------------------------------------------------------------
    private ao.supervised.decision.input.raw.example.Example function(
            boolean   isPositive,
            double... input)
    {
        return context(input).withTarget(
                new Datum(isPositive));
    }

    private Context context(double... vars)
    {
        List<Datum> varAttributes = new LinkedList<Datum>();
        int         type          = 0;
        for (double var : vars)
        {
            varAttributes.add(
                    new Datum(String.valueOf(type++), var));
        }
        return new ContextImpl(varAttributes);
    }
}
